<?php
/**
* Базовый клас работы с моделями
* АктивРекорд.
* Фабрика создания объектов базы данных.
* @author manfredi
*/
class Model extends Base {
	public    $__db_object = true;
	# Название таблицы
	protected $__db_table = "";
	# Массив изменений полей
	protected $__changes = Array();
	# При Save() требуется ли инсерт
	protected $__insert = false;
	# При Save() были ли какие то изменения
	protected $__update = false;
	# Инициализирован ли объект
	public    $__initialized = false;
	# Работа с базой данных.
	protected $___systems = Array("__changes" => 1, "__insert" => 1, "__update" => 1);
	
	
	public $dbh;
	# Работа с кешом
	public $mch;

	public static $__cache_many = false;
	/*
	* Массив группы ключей
	* Определяет какие ключи надо скинуть при изменени или delete();
	*/
	public $__cache_group = Array();

	public function add_cache_group($key){
		$this->__cache_group[] = $key;
	}

	public function on_delete_before(){}
	public function on_delete(){}
	public function on_update_before(){}
	public function on_update(){}
	public function on_insert_before(){}
	public function on_insert(){}
	
	public function getTrace(){
		//$trace = "Traced:<br />";
		/*foreach(debug_backtrace() as $key => $val){
			if($key > 0){
				$trace .= "File: {$val["file"]} in line {$val["line"]};"
				.($val["object"])? "Object: {$val["object"]} " : ""
				." function: {$val["function"]} (".(!empty($val["args"])? join(",", $val["args"]) : "EMPTY").")<br />";
			}		
		}*/
		return $trace;
	}
	
	function __construct($insert = false){
		$this->__insert = $insert;
		$this->__db_table = get_class($this);
	}

    /**
    * Фиксация изменений ..
    *
    * @param string $var
    * @param mixed $value
    */
    public function __change ($var, $value) {
        $property = "_".$var;
        $table = $this->__db_table;
        if (property_exists($this, $property) ) {
            if (
            // Параметр реально изменился
            	$this->$property != $value &&
            // Параметр есть в списке полей
            	isset($table::$__fields[$var]) &&
            // Параметр еще не менялся - так как надо обновить версию тега для предыдущего значения
            	!isset($this->__changes[$var]) && 
            	$this->__initialized
               )
            {
                $this->__changes[$var] = $this->$property;
                $this->__update=true;
            }
        }
    }

    /*function creatWhereFromUpdate(){
        $keymap = implode(" = ? AND ",)." = ?";
        return $keymap;
    }*/
    /*
    *  Сохронить измененные кешь данные
    */

    public function save(){
        if($this->__update == true){
        	$this->on_update_before();
        	# Создаем запрос.
        	$table = $this->__db_table;
            $query = new Query();
            $sql = "Update ". $this->__db_table ." SET ";
            foreach($this->__changes as $field => $value){
                if(isset($table::$__fields[$field]) && !isset($table::$__pKey[$field])){
                    $query->fieldSet($field, $this->$field);
                }
            }
            $sql.= $query->toStringSet();
			foreach($table::$__pKey as $val){
            	$query->field($val , $this->$val);
            }
            $sql.= $query->toString();

			$sth = DB::getDBH()->prepare($sql);
            $sth->execute($query->getValue());
        	if( $sth->rowCount() == 0 ){
        		Tagger::update($table, $this );
           		Worker::deleteRunTime( $this->generateMcID() );
           		throw new ModelException("Object '{$table}' no Update");
           	}else{
           		Tagger::update($table, $this );
           		if(!MY_PDO::$isTrance){
					$this->saveToCache();
				}else{
					array_push(MY_PDO::$trancObject, $this);
				}
           	}
           	
           	Worker::$_worker_stat["SQLUpdateCNT"]++;
			Worker::$_worker_stat["SQLUpdate"][]=Array( $table, $sql, $query->getValue());

			//Tagger2::update($table, $this );
			$this->on_update();
            $this->__update = false;
            $this->__changes = Array();
        }elseif($this->__insert == true){
        	$this->on_insert_before();
			$table = $this->__db_table;
			$this->getNextVal($table);
			$fil = Array();
            $val = Array();
            foreach($table::$__fields as $key => $value)
            {
                if($this->$key!=""){
                    $fil[] = $key;
                    $filv[]= "?";
                    $val[] = $this->$key;
                    $ctach[$key] = $value;
                }

            }

			if(count($val)>0){
				//try{
	                $sql = "Insert into " . $this->__db_table . " ( " . implode(",",$fil) . " ) VALUES (". implode(",",$filv) .")";
	                #echo $sql;
	                #var_dump($val);
	                $sth = DB::getDBH()->prepare($sql);
	                $sth->execute($val);
	                $this->__insert=false;
	                $this->__initialized = true;
	                Worker::$_worker_stat["SQLInsertCNT"]++;
					Worker::$_worker_stat["SQLInsert"][]=Array( $table, $sql, $val);
					Tagger::update($table, $this );
           			if(!MY_PDO::$isTrance){
						$this->saveToCache();
					}else{
						array_push(MY_PDO::$trancObject, $this);
					}
					$this->on_insert();
					//Tagger2::update($table, $this );
				//}
				//catch(Exception $e){
				//	$this->fetchOneByField($ctach);
				//}
            }

        }else{
            #echo "Методу Save нечего делать";
        }
    }


    /**
    *
    *
    */
    private function getNextVal(){
    	$table = $this->__db_table;
    	if(count($table::$__key_pk_seq) > 0){
    		foreach($table::$__key_pk_seq as $key => $seq){
    			$sql="Select nextval('".$seq."') as ruturn";
        		$sth = DB::getDBH()->query($sql);
        		$row=$sth->fetch(PDO::FETCH_ASSOC);
        		$this->$key = $row["ruturn"];
    		}
    	}

    }

    /**
    * Обновление объекта в кеше
    * Принудительное внесение объекта в кешь, если таблица это разрешает
    */
    public function saveToCache(){
        $cache_array = Array();
        $cache_key   = $this->generateMcID();
        $table = $this->__db_table;
        foreach($table::$__fields as $key => $value){
            $cache_array[$key] = $this->$key;
        }
        if(@DB::getMCH()->get($cache_key) !== false){
            DB::getMCH()->replace( $cache_key , json_encode($cache_array),0, 3600*10 );
        }else{
            @DB::getMCH()->set( $cache_key , json_encode($cache_array),0, 3600*10 );
        }
        /*if(core::$user->id == 42){
			echo "saveToCache<br />";
        }*/
        //Worker::deleteRunTime($cache_key);
    }

    /**
    * Очистка кеша от объекта
    */
    public function clear(){
    	$table = $this->__db_table;
        if($table::$__cached_table){
	        $cache_key =  $this->generateMcID();
        	if(DB::getMCH()->get($cache_key) !== false){
           		 DB::getMCH()->delete($cache_key,0);
                 Worker::deleteRunTime($cache_key);
			}
		}
    }

    /*Удалить объект полностью*/
    function delete(){
		if($this->__initialized == true){
			$this->on_delete_before();
			$this->clear();
			$table = $this->__db_table;
			$query = new Query();
            foreach($table::$__pKey as $val){
            	$query->field( $val, $this->$val );
            }
			$sql = "DELETE FROM ".$this->__db_table ." ". $query->toString();
			//echo "$sql";
			//echo "<pre>";
			//print_r($query->getValue());
            $sth = DB::getDBH()->prepare($sql);
            $sth->execute($query->getValue());
            //Tagger::update($table, $this );
			$this->__update = false;
            $this->__changes = Array();
			
           	if( $sth->rowCount() == 0 ){
           		Tagger::update($table, $this );
           		Worker::deleteRunTime( $this->generateMcID() );
           		throw new ModelException("Object is deleted  before");
           		return false;
           	}/**/
           	else{
           		Tagger::update($table, $this );
           		Worker::deleteRunTime( $this->generateMcID() );
           		$this->on_delete();
           	}

            return true;
		}
		return false;
    }

    public function delCacheGroup(){
    	foreach($this->__cache_group as $key){
           	Worker::deleteRunTime($key);
           	Worker::deleteManyCache($key);
        }
    }

    /**
    * Генерирует перфикс ключа для объекта
    * Основываваясь на количестве записей о примари кей
    * @param mixed $fields
    */
    function generateMcID(){
    	$fields = Array();
    	$table = $this->__db_table;
    	foreach($table::$__pKey as $value){
			$fields[$value]	= $this->$value;
		}

    	return Worker::genOID($this->__db_table,$fields);

    }

    /**
    * Конвертировать объекты в массивы
    *
    * @param mixed $recursive - Рекурсивно
    * @param mixed $var - Только определенную ветку
    * @param mixed $isLoad - Если ветка не загружена загрузить
    */
    function toArray($recursive = false, $var = null, $isLoad = false)
    {
    	$isLoad = false;
		$table = $this->__db_table;
        $arr = Array();
        if($var == null){
	        foreach($table::$__fields as $key => $value){
	            $arr[$key] = $this->$key;
	        }
	        if($recursive){
	            $aRelated = array_diff(preg_filter("/^_(.+)$/", "$1", preg_grep("/^_/", preg_grep("/^__/", array_keys(get_class_vars(get_class($this))), PREG_GREP_INVERT))), array_keys($table::$__fields));
	            foreach($aRelated as $fld){
	            	if($isLoad){
									$this->loadRelated($fld,$recursive);
								}
	                if($this->{'_' . $fld} != null){

	                    if(is_array($this->{'_' . $fld})){
	                        foreach($this->{'_' . $fld} as $key => $value){
	                            if(is_object($value) && method_exists($value, 'toArray')){
	                                $arr[$fld][$key] = $value->toArray($recursive);
	                            }
	                        }
	                    }elseif(is_object($this->{'_' . $fld}) && method_exists($this->{'_' . $fld}, 'toArray'))
	                    {
	                    	##var_dump($this->{'_' . $fld});
	                        $arr[$fld] = $this->{'_' . $fld}->toArray($recursive);
	                    }
	                }

	            }
	        }
		}elseif(property_exists($this, "_".$var)){
			if($isLoad){
				$this->loadRelated($var,$recursive);
			}

			if($this->{'_' . $var} != null){
	            if(is_array($this->{'_' . $var})){
	                foreach($this->{'_' . $var} as $key => $value){
	                    if(is_object($value) && method_exists($value, 'toArray')){
	                        $arr[$key] = $value->toArray($recursive);
	                    }
	                }
	            }elseif(is_object($this->{'_' . $var}) && method_exists($this->{'_' . $var}, 'toArray')){
	                $arr[$var] = $this->{'_' . $var}->toArray($recursive);
	            }
	        }
		}
        return $arr;
    }

    /**
    * Загрузить связи
    * @param mixed $var - только определенную связ
    * @param mixed $recursive - реверсивно по цепочке
    */
    function loadRelated($var = null,$recursive = false){
    	$table = $this->__db_table;
		if($var == null){
			$aRelated = array_diff(preg_filter("/^_(.+)$/", "$1", preg_grep("/^_/", preg_grep("/^__/", array_keys(get_class_vars(get_class($this))), PREG_GREP_INVERT))), array_keys($table::$__fields));
			foreach($aRelated as $fld){
				if($this->{'_' . $fld} == null)	{
					$this->{'get_' . $fld}();
					if(is_object($this->{'_' . $fld}) && method_exists($this->{'_' . $fld}, 'loadRelated')){
						$this->{'_' . $fld}->loadRelated($recursive);
					}elseif(is_array($this->{'_' . $fld})){
						foreach($this->{'_' . $fld} as $key => $value){
                    		if(is_object($value) && method_exists($value, 'loadRelated')){
                        		$arr[$fld][$key] = $value->loadRelated(null,false);
	                        }
						}
					}
				}
			}
		}elseif(property_exists($this, "_".$var)){
			if($this->{"_" . $var} == null){
				$this->{"get_" . $var}();
			}
			if(is_object($this->{"_" . $var}) && method_exists($this->{'_' . $var}, 'loadRelated') && $recursive){
				$this->{'_' . $var}->loadRelated(null,false);
			}
			elseif(is_array($this->{'_' . $var}) && $recursive){
				foreach($this->{'_' . $var} as $key => $value){
                	if(is_object($value) && method_exists($value, 'loadRelated')){
                    	$arr[$var][$key] = $value->loadRelated(null,false);
	            	}
				}
			}
		}
	}

}


?>
